#include "thread.h"

#define EINVAL 3
#define ESRCH 4

struct thread {
  ucontext_t* uc;
  void* retval;
  thread_t* join;
  int valgrind_stackid;
};

//Liste des threads qui tournent
static struct list* runq=NULL;

//Liste des valeurs de retour
static struct list* deathq=NULL;

thread_t thread_self(void){
  if(runq==NULL){
    //Initialistation du thread du main
    runq=list_create();
    ucontext_t *uc=malloc(sizeof(ucontext_t));
    if (uc==NULL) return NULL;
    thread_t *tempt=malloc(sizeof(thread_t));
    if (tempt==NULL) return NULL;
    struct thread * main_thread=malloc(sizeof(struct thread));
    if (main_thread==NULL) return NULL;
    main_thread->uc=uc;
    main_thread->join=NULL;
    *tempt=(void*)main_thread;
    //On l'ajoute à la runq
    list_add(&runq,tempt);
  }
  return *(runq->t1);
}

int thread_create(thread_t *newthread, void *(*func)(void *), void *funcarg){
  //Initialisation du nouveau thread
  ucontext_t *uc=malloc(sizeof(ucontext_t));
  if (uc==NULL) return -1;
  getcontext(uc);
  uc->uc_stack.ss_size = 64*1024;
  uc->uc_stack.ss_sp = malloc(uc->uc_stack.ss_size);
  if (uc->uc_stack.ss_sp==NULL) return -1;
  int valgrind_stackid = VALGRIND_STACK_REGISTER(uc->uc_stack.ss_sp,uc->uc_stack.ss_sp + uc->uc_stack.ss_size);
  makecontext(uc, (void (*)(void)) func, 1,funcarg);
  struct thread * new_thread=malloc(sizeof(struct thread));
  if (new_thread==NULL) return -1;
  new_thread->uc=uc;
  new_thread->join=NULL;
  new_thread->valgrind_stackid=valgrind_stackid;
  *newthread=(void *)new_thread;
   
  if (runq==NULL){ //Si le thread main n'a pas était créer
    //Initialistation du thread du main
    runq=list_create();
    ucontext_t *ucm=malloc(sizeof(ucontext_t));
    if (ucm==NULL) return -1;
    thread_t *tempt=malloc(sizeof(thread_t));
    if (tempt==NULL) return -1;
    struct thread * main_thread=malloc(sizeof(struct thread));
    if (main_thread==NULL) return -1;
    main_thread->uc=ucm;
    main_thread->join=NULL;
    *tempt=(void*)main_thread;
    //On l'ajoute à la runq
    list_add(&runq,tempt);
    //On ajoute le nouveau thread à la liste
    list_add(&runq,newthread);
    //On lance le thread
    swapcontext(ucm,uc);
  }
  else{
    //On ajoute le nouveau thread à la liste
    list_add(&runq,newthread);
    //On lance le thread
    swapcontext(((struct thread*)*(runq->previous->t1))->uc,uc);
  }   
  return EXIT_SUCCESS;
}

int thread_yield(void){
  //On regarde si le changement de thread est utile
  if (runq!=NULL || runq->next==runq){
    // On met le thread actuel en dernière position dans la runq et le nouveau thread en première position
    runq = runq->next;
    // On l'ancien thread et on active le nouveau
    swapcontext(((struct thread*)*(runq->previous->t1))->uc,((struct thread*)*(runq->t1))->uc);
  }
  return EXIT_SUCCESS;
}

int thread_join(thread_t thread, void **retval){
  struct list *current=NULL;
  //On verifi que le thread n'attend pas quelqu'un d'autre
  if(((struct thread*)thread)->join!=NULL){
    // On retourne que le join est déja fait.
    return EINVAL;
  }

  //2 cas : 
  //Cas 1 : le thread a fini et est dans la deathq
  if(deathq!=NULL){
    current=deathq;
    while(*(current->t1)!=thread){
      current=current->next;
      if(current==deathq){
	current=NULL;
	break;
      }
    }
  }
  if(current!=NULL){
    //On supprime le thread de la deathq
    deathq=current;
    list_remove(&deathq);
  }

  //Cas 2 : le thread n'a pas encore fini et on se met en sommeille (ie. on ne l'a pas trouvé dans la deathq)
  else{
  //printf("J'ai pas trouvé donc j'attend %p\n",thread_self());
    //On attache le thread courant au thread qu'on attend  
    ((struct thread*)thread)->join=runq->t1;
    // On désactive le contexte actuel et active le contexte suivant
    list_remove(&runq);
    swapcontext(((struct thread*)*((struct thread*)thread)->join)->uc,((struct thread*)*(runq->t1))->uc);
    //On retourne ici
  }
	
  //Finalement on récupére la valeur de retour et on libére la mémoire
  (*retval)=((struct thread*)thread)->retval;
 // printf("J'ai finalement trouvé %li je suis %p\n",*(long int*)*retval,thread_self());
  VALGRIND_STACK_DEREGISTER(((struct thread*)thread)->valgrind_stackid);
  free(((struct thread*)thread)->uc->uc_stack.ss_sp);
  free(((struct thread*)thread)->uc);
  free((struct thread*)thread);
  return EXIT_SUCCESS;
}
  
void thread_exit(void *retval){
  //On d'abord modifi la valeur de retour
  ((struct thread*)*(runq->t1))->retval=retval;

  //On regarde ensuite si un thread nous attend 
  if(((struct thread*)*(runq->t1))->join!=NULL){
    //Si c'est le cas on le reveille
    list_add(&runq,((struct thread*)*(runq->t1))->join);
    //Et on repositionne la tête de la runq car l'ajout se fait en tête
    runq=runq->previous;
  }
  //Sinon on ajoute le thread à la deathq
  else{
    if (deathq==NULL){
      deathq=list_create();
    }
    list_add(&deathq,runq->t1);
  }

  //Finalement on le supprime de la runq
  list_remove(&runq);
  //Et on active le thread suivant
  setcontext(((struct thread*)*(runq->t1))->uc);
}







//Fonction pour debug
void printanyq(struct list * anyq){
  struct list *current=NULL;
  if(anyq!=NULL){
    printf("This q containt :\n");
    current=anyq;
    do{
      printf("%p\n",*(current->t1));
      printf("join : %p\n",(((struct thread*)*(current->t1))->join==NULL)?NULL:*((struct thread*)*(current->t1))->join);
      current=current->next;
    }
    while(current!=anyq);
  }
  else{
    printf("This q is empty\n");
  }
}

void printq(void){
  printf("Runq:");
  printanyq(runq);
  printf("Deathq:");
  printanyq(deathq);
}

void main_free(void){
  //On regarde si il ne resterai pas que le main dans la runq
  //Et qu'il n'y a plus de thread mort
  if(runq==runq->next && deathq==NULL){
    free(((struct thread *)*(runq->t1))->uc);        
    free((struct thread*)*(runq->t1));	
    free(runq->t1);
    list_remove(&runq);
  }
}
